"""

This module provides some helper functions for PyQt5 widgets.

Author: xxd

version: 1.1.0
新增功能：
- 新增set_widget_background_full方法，背景图片按窗口大小拉伸填满窗体
- 新增set_widget_background_regular方法，背景图片按原大小铺满窗体
- 新增get_qt_app方法，获取Qt应用程序实例
- 三大控件添加menu方法，可设置菜单项的点击事件

"""
import datetime
import enum
import os
import sys
from pathlib import Path
from typing import Optional, Union, Callable, Any
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QMediaPlaylist


class PlayMode(enum.IntEnum):
    """
    播放模式
    """
    # 播放一次
    Once = QMediaPlaylist.CurrentItemOnce
    # 当前项目在循环中
    CurrentItemInLoop = QMediaPlaylist.CurrentItemInLoop
    # 顺序播放
    Sequential = QMediaPlaylist.Sequential
    # 循环播放
    Loop = QMediaPlaylist.Loop
    # 随机播放
    Random = QMediaPlaylist.Random


class TMediaPlayer:
    """
    媒体播放器
    """

    def __init__(self, file: Optional[str] = None):
        self.media_player = QMediaPlayer()
        self.set_media_file(file)

    def set_play_mode(self, mode: PlayMode):
        """
        设置播放模式
        """
        self.media_player.playlist() \
            .setPlaybackMode(QMediaPlaylist.PlaybackMode(mode))

    def set_media_file(self, file: str):
        """
        设置媒体文件
        """
        # 如果文件存在
        if file and os.path.exists(file):
            # 设置媒体文件
            self.media_player.setMedia(
                QMediaContent(QtCore.QUrl.fromLocalFile(os.path.abspath(file))))

    # 播放
    def play(self):
        """
        播放
        """
        # 如果媒体文件不为空
        if not self.media_player.media().isNull():
            # 播放
            self.media_player.play()

    # 暂停
    def pause(self):
        """
        暂停播放
        """
        self.media_player.pause()

    # 停止
    def stop(self):
        """
        停止播放
        """
        self.media_player.stop()

    # 设置音量
    def set_volume(self, val: int):
        """
        设置音量
        """
        # 如果音量在0-100之间
        if 0 <= val <= 100:
            # 设置音量
            self.media_player.setVolume(val)

    # 添加媒体文件
    def add_media_file(self, file: str):
        """
        添加媒体文件
        """
        # 添加文件到播放列表
        self.add_media_list([file])

    # 添加媒体列表
    def add_media_list(self, file_list: Union[list, tuple]):
        """
        添加媒体列表
        """
        self.media_player.playlist().addMedia([
            QMediaContent(QtCore.QUrl.fromLocalFile(os.path.abspath(file)))
            for file in file_list if os.path.exists(file)
        ])



def set_widget_background_full(widget: QtWidgets.QWidget, img_path: str, resize_event: bool = False):
    """
    背景图片按窗口大小拉伸填满窗体
    注意：如果需要在调整窗体大小时，背景图片也跟着调整，可以设置resize_event=True
    本函数会重写resizeEvent方法，如果不需要，请设置resize_event=False
    **推荐使用**set_widget_background_full_auto方法。
    Args:
        widget (QWidget):设置的目标对象
        img_path (str): 图片路径
        resize_event (bool): 是否在调整窗体大小时，背景图片也跟着调整
    """
    _old_resize_event = widget.resizeEvent
    def _resize_event(obj, e):
        palette = QtGui.QPalette()
        palette.setBrush(widget.backgroundRole(), QtGui.QBrush(
            QtGui.QPixmap(img_path).scaled(
                widget.size(),
                QtCore.Qt.IgnoreAspectRatio,
                QtCore.Qt.SmoothTransformation
            )))
        widget.setPalette(palette)
        _old_resize_event(obj, e)

    _resize_event(widget, None)
    if resize_event:
        widget.resizeEvent = _resize_event

def set_widget_background_full_auto(widget: QtWidgets.QWidget, img_path: str):
    """
    背景图片按窗口大小拉伸填满窗体，推荐使用此方法，无效时使用set_widget_background_full方法
    """
    img_path = Path(img_path).resolve().as_posix()
    widget.setStyleSheet("border-image: url(%s) 0 0 0 0 stretch stretch;" % img_path)

def set_widget_background_regular_auto(widget: QtWidgets.QWidget, img_path: str):
    """
    背景图片按原大小铺满窗体，推荐使用此方法，无效时使用set_widget_background_regular方法
    """
    img_path = Path(img_path).resolve().as_posix()
    widget.setStyleSheet("background-image: url(%s) 0 0 0 0 stretch stretch;" % img_path)

def set_widget_background_regular(widget: QtWidgets.QWidget, img_path: str):
    """
    背景图片按原大小铺满窗体，推荐使用set_widget_background_regular_auto，无效时使用本方法

    Args:
        widget (QWidget):设置的目标对象
        img_path (str): 图片路径
    """
    palette = QtGui.QPalette()
    palette.setBrush(QtGui.QPalette.Background, QtGui.QBrush(QtGui.QPixmap(img_path)))
    widget.setPalette(palette)

def set_widget_background_gif(widget: QtWidgets.QWidget, img_path: str) -> bool:
    """
    设置窗口部件的GIF动画背景。

    Args:
        widget (QtWidgets.QWidget): 要设置背景的窗口部件
        img_path (str): GIF图片的路径

    Returns:
        bool: 设置是否成功。如果图片不存在或设置失败则返回False，成功返回True

    Examples:
        >>> label = QLabel()
        >>> set_widget_background_gif(label, "animation.gif")
    """
    # 验证输入参数
    if not widget or not img_path:
        return False
        
    if not os.path.exists(img_path):
        return False
        
    try:
        movie = QtGui.QMovie(img_path)
        if movie.isValid():
            if isinstance(widget, QtWidgets.QLabel):
                # 对于QLabel直接设置动画
                label = widget
                label.setAlignment(Qt.AlignCenter)
                label.setMovie(movie)
                label.setScaledContents(True)
                
            else:  # 对于其他QWidget创建子QLabel
                label = QtWidgets.QLabel(widget)
                label.setObjectName("_background_gif_label")
                label.setGeometry(0, 0, widget.width(), widget.height())
                label.setAlignment(Qt.AlignCenter)
                label.setMovie(movie)
                label.setScaledContents(True)
                label.lower()
                # 根据窗口部件大小调整动画大小
                movie.setScaledSize(QtCore.QSize(widget.width(), widget.height()))
                
                # 设置背景
                palette = widget.palette()
                palette.setBrush(QtGui.QPalette.Background, 
                               label.palette().brush(QtGui.QPalette.Background))
                widget.setPalette(palette)
                
                # 确保label随父窗口调整大小
                widget.resizeEvent = lambda e: label.setGeometry(
                    0, 0, widget.width(), widget.height())
                
            movie.start()
            return True
            
    except Exception as e:
        print(f"设置GIF背景失败: {str(e)}")
        return False
        
    return False

def set_window_icon(widget: QtWidgets.QWidget, icon_path: str, ico_text: str=''):
    """
    设置窗口的图标和图标文本。

    此函数用于为给定的窗口小部件设置图标。如果指定了图标文本，也会设置图标文本。
    仅当给定的路径中存在的图标文件时，才会尝试设置图标。

    参数:
    - widget: 要设置图标的窗口小部件。
    - icon_path: 图标文件的路径。
    - ico_text: （可选）图标的文本标签。

    返回:
    无返回值。如果图标文件不存在或widget不是QWidget的实例，则不进行任何设置。
    """
    # 检查指定的图标路径是否存在，如果不存在，则函数不执行任何操作。
    if not os.path.exists(icon_path):
        return
    # 确保传入的widget是QWidget的实例，确保可以安全地调用setWindowIcon方法。
    if isinstance(widget, QtWidgets.QWidget):
        # 设置窗口图标为指定路径的图标。
        widget.setWindowIcon(QtGui.QIcon(icon_path))
        # 如果提供了图标文本，则设置窗口的图标文本。
        if ico_text:
            widget.setWindowIconText(ico_text)



class SelectionMode(enum.IntEnum):
    """
    选择模式
    Single 每次只选一个
    Multi 可以进行多选
    NoSelection 不可选择
    """
    Single = QtWidgets.QAbstractItemView.SingleSelection
    Multi = QtWidgets.QAbstractItemView.MultiSelection
    NoSelection = QtWidgets.QAbstractItemView.NoSelection


class SelectionBehavior(enum.IntEnum):
    """
    选择行为
    SelectItems 按项进行选择
    SelectRows 按行进行选择
    SelectColumns 按列进行选择
    """
    SelectItems = QtWidgets.QAbstractItemView.SelectItems
    SelectRows = QtWidgets.QAbstractItemView.SelectRows
    SelectColumns = QtWidgets.QAbstractItemView.SelectColumns


class EditTriggers(enum.IntEnum):
    """
    编辑触发器
        NoEditTriggers 没有触发器
        CurrentChanged 当前行改变时触发
        DoubleClicked 双击时触发
        SelectedClicked 选择单元格时触发
        EditKeyPressed 按下编辑键时触发
    """
    NoEditTriggers = QtWidgets.QAbstractItemView.NoEditTriggers
    CurrentChanged = QtWidgets.QAbstractItemView.CurrentChanged
    DoubleClicked = QtWidgets.QAbstractItemView.DoubleClicked
    SelectedClicked = QtWidgets.QAbstractItemView.SelectedClicked
    EditKeyPressed = QtWidgets.QAbstractItemView.EditKeyPressed
    AnyKeyPressed = QtWidgets.QAbstractItemView.AnyKeyPressed
    AllEditTriggers = QtWidgets.QAbstractItemView.AllEditTriggers


class _EventAgency:
    """
    事件代理
    """

    def __init__(self):
        self.event_list = {}

    def call_fun(self, event: str, *args, **kwargs):
        fun = self.event_list.get(event, None)
        print('call_fun: ', event, *args, **kwargs)
        if callable(fun):
            return fun(*args, **kwargs)

    def register_fun(self, event: str, fun: Callable):
        self.event_list[event] = fun

    def clear(self):
        self.event_list.clear()

class TWidgetItemDelegate(QtWidgets.QItemDelegate):
    IntMinimum = -100000
    IntMaximum = 100000
    IntSingleStep = 1
    FloatMinimum = -100000.0
    FloatMaximum = 100000.0
    FloatSingleStep = 0.1

    def createEditor(self, parent, option, index):
        # 创建一个QLineEdit对象
        if index.data(Qt.UserRole) == 'str':
            editor = QtWidgets.QLineEdit(parent)
        elif index.data(Qt.UserRole) == 'int':
            editor = QtWidgets.QSpinBox(parent)
            editor.setMinimum(self.IntMinimum)
            editor.setMaximum(self.IntMaximum)
            editor.setSingleStep(self.IntSingleStep)

        elif index.data(Qt.UserRole) == 'float':
            editor = QtWidgets.QDoubleSpinBox(parent)
            editor.setMinimum(self.FloatMinimum)
            editor.setMaximum(self.FloatMaximum)
            editor.setSingleStep(self.FloatSingleStep)
        elif index.data(Qt.UserRole) == 'bool':
            editor = QtWidgets.QCheckBox(parent)

        else:
            editor = QtWidgets.QLineEdit(parent)
        return editor

    def setEditorData(self, editor, index):
        # 获取单元格的数据
        value = index.data(Qt.DisplayRole)
        # 设置编辑框的文本
        if index.data(Qt.UserRole) == 'str':
            editor.setText(str(value))
        elif index.data(Qt.UserRole) == 'int':
            editor.setValue(int(value))
        elif index.data(Qt.UserRole) == 'float':
            editor.setValue(float(value))
        elif index.data(Qt.UserRole) == 'bool':
            editor.setChecked(bool(value))
        else:
            editor.setText(str(value))

    def setModelData(self, editor, model, index):
        # 获取编辑框的文本
        if index.data(Qt.UserRole) == 'str':
            text = editor.text()
        elif index.data(Qt.UserRole) == 'int':
            text = editor.value()
        elif index.data(Qt.UserRole) == 'float':
            text = editor.value()
        elif index.data(Qt.UserRole) == 'bool':
            text = editor.isChecked()
        else:
            text = editor.text()

        # 设置单元格的数据
        model.setData(index, text)

    def updateEditorGeometry(self, editor, option, index):
        # 设置编辑框的大小和位置
        editor.setGeometry(option.rect)

class BaseDataWidget(QtCore.QObject):
    clicked = QtCore.pyqtSignal(int, int, str)
    double_clicked = QtCore.pyqtSignal(int, int, str)

    def __init__(self,
                 widget: QtWidgets.QAbstractItemView,
                 data: Optional[Union[tuple, list]] = None,
                 edit: bool = False,
                 select_mode: SelectionMode = SelectionMode.Single,
                 select_behavior: SelectionBehavior = SelectionBehavior.SelectRows,
                 menu: Optional[QtWidgets.QMenu] = None,
                 parent: Optional[QtCore.QObject] = None):
        super().__init__(parent)
        # if not widget:
        #     return
        self.widget = widget
        if self.widget.objectName():
            self.setObjectName("_" + self.widget.objectName())
        self.editable = edit
        self.widget.setEditTriggers(
            QtWidgets.QAbstractItemView.DoubleClicked if edit else QtWidgets.QAbstractItemView.NoEditTriggers)
        self.widget.setItemDelegate(TWidgetItemDelegate(self.widget))
        self.widget.installEventFilter(self)
        self.menu = menu
        self.widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.widget.customContextMenuRequested.connect(self.show_menu)
        self.set_data(data)
        self.widget.setAutoScroll(True)
        self.widget.setVerticalScrollMode(
            QtWidgets.QAbstractItemView.ScrollPerItem)
        self.widget.setHorizontalScrollMode(
            QtWidgets.QAbstractItemView.ScrollPerPixel)
        self.select_mode = select_mode
        self.select_behavior = select_behavior
        self._set_select_status()
        self._event = _EventAgency()
        self.widget.clicked.connect(self.item_clicked)
        self.widget.doubleClicked.connect(self.item_double_clicked)

    def _set_select_status(self):
        self.set_selection_mode(self.select_mode)
        self.set_selection_behavior(self.select_behavior)

    def set_data(self, data: Union[tuple, list]):
        pass

    def set_db_data(self, data: Union[tuple, list]):
        pass

    def get_data(self):
        pass

    def set_selection_mode(self, mode: SelectionMode):
        self.widget.setSelectionMode(mode)

    def set_selection_behavior(self, behavior: SelectionBehavior):
        self.widget.setSelectionBehavior(behavior)

    def set_edit_triggers(self, triggers: EditTriggers):
        self.widget.setEditTriggers(triggers)

    def get_current_row(self):
        return self.widget.currentIndex().row()

    def get_current_col(self):
        return self.widget.currentIndex().column()

    def get_current_text(self, role: int = QtCore.Qt.DisplayRole):
        return str(self.widget.currentIndex().data(role))

    def has_focus(self):
        return self.widget.hasFocus()

    def has_selected(self):
        return len(self.widget.selectedIndexes()) > 0

    def get_row_count(self):
        return self.widget.model().rowCount()

    def get_col_count(self):
        return self.widget.model().columnCount()

    def clear(self):
        cl = getattr(self.widget, 'clear', None)
        if callable(cl):
            cl()
        self.widget.reset()

    def clear_focus(self):
        self.widget.clearFocus()

    def set_focus(self):
        self.widget.setFocus()

    @QtCore.pyqtSlot()
    def item_clicked(self):
        self._set_select_status()
        self.clicked.emit(self.get_current_row(),
                          self.get_current_col(),
                          self.get_current_text())
        return self._event.call_fun('clicked',
                                   self.get_current_row(),
                                   self.get_current_col(),
                                   self.get_current_text())

    @QtCore.pyqtSlot()
    def item_double_clicked(self):
        self._set_select_status()
        self.double_clicked.emit(self.get_current_row(),
                                 self.get_current_col(),
                                 self.get_current_text())
        return self._event.call_fun('double_clicked',
                                   self.get_current_row(),
                                   self.get_current_col(),
                                   self.get_current_text())

    def set_func_item_clicked(self, func: Callable):
        self._event.register_fun('clicked', func)

    def set_func_item_double_clicked(self, func: Callable):
        self._event.register_fun('double_clicked', func)

    def eventFilter(self, a0: QtCore.QObject, a1: QtCore.QEvent):
        # print(a0.objectName(), a1.type())
        # 过滤键盘ctrl按钮按下事件
        if a1.type() == QtCore.QEvent.KeyPress:
            key = a1.key()
            if key == QtCore.Qt.Key_Control:
                self.set_selection_mode(SelectionMode.Multi)
                return True
            else:
                return False
        elif a1.type() == QtCore.QEvent.KeyRelease:
            key = a1.key()
            if key == QtCore.Qt.Key_Control:
                self.set_selection_mode(SelectionMode.Single)
                # print(self.get_selected())
                return True
            else:
                return False
        else:
            return super().eventFilter(a0, a1)

    def show_menu(self, pos):
        if self.menu:
            self.menu.exec_(self.widget.mapToGlobal(pos))

    def set_menu(self, menu: QtWidgets.QMenu):
        if menu and isinstance(menu, QtWidgets.QMenu):
            self.menu = menu

    def _set_menu_data(self, menu: QtWidgets.QMenu, data: dict):
        """
        action data
        {
            'title': {
                'func': func,
                'icon': 'icon path'
                'sub_menu': {
                    'title': {
                        'func': func
                    }
                }
            },
            'title': {
                'func': func,
                'sub_menu': {
                    'title': {
                        'func': func
                    }
                }
            },
            'title': func
        }
        """
        for key, value in data.items():
            if isinstance(value, dict):

                func = value.get('func')
                if callable(func):
                    icon = value.get('icon', '')
                    if icon:
                        menu.addAction(QtGui.QIcon(icon), key, func)
                    else:
                        menu.addAction(key, func)
                sub_menu = value.get('sub_menu')
                if sub_menu:
                    sub_menu = QtWidgets.QMenu(key)
                    menu.addMenu(sub_menu)
                    self._set_menu_data(sub_menu, value)
            elif callable(value):
                menu.addAction(key, value)
            else:
                return
        self.menu.triggered.connect(self._on_menu_triggered)

    def _on_menu_triggered(self, a0: QtWidgets.QAction):
        """
        用于测试与扩展
        """
        print(f'action {a0.text()} clicked')

    def set_menu_data(self, data, title: str = ''):
        """
        设置菜单数据
        action data
        {
            'title': {
                'func': func,
                'icon': 'icon path'
                'sub_menu': {
                    'title': {
                        'func': func
                    }
                }
            },
            'title': {
                'func': func,
                'sub_menu': {
                    'title': {
                        'func': func
                    }
                }
            },
            'title': func
        }
        """
        if self.menu:
            self.menu.clear()
        else:
            self.menu = QtWidgets.QMenu(self.widget)
        self.menu.setTitle(title)
        self._set_menu_data(self.menu, data)


def get_db_data_type(data):
    """
    判断传入数据类型
    """
    if data:
        if isinstance(data[0], dict):
            return 1  # as_dict
        elif len(data) == 2 and \
                (isinstance(data[0], list) or isinstance(data[0], tuple)) and \
                (isinstance(data[1], list) or isinstance(data[1], tuple)):
            return 2  # as_list
        else:
            return  # err
    else:
        return  # err


class TListWidget(BaseDataWidget):
    def __init__(self,
                 list_widget: QtWidgets.QListWidget,
                 tlist: Optional[Union[list, tuple]] = None,
                 edit: bool = False,
                 select_mode: SelectionMode = SelectionMode.Single,
                 select_behavior: SelectionBehavior = SelectionBehavior.SelectRows,
                 parent: Optional[QtCore.QObject] = None):
        super(TListWidget, self).__init__(list_widget, tlist, edit,
                                          select_mode, select_behavior, parent)

    def set_data(self, data: Union[tuple, list]):
        """
        设置列表控件的内容
        """
        self.clear()
        try:
            if data:
                for s in data:
                    item = QtWidgets.QListWidgetItem(self.widget)
                    item.setText(str(s))
                    item.setData(Qt.UserRole, s.__class__.__name__)

                if self.editable:
                    for i in range(self.widget.count()):
                        self.widget.item(i).setFlags(item.flags() | Qt.ItemIsEditable )
        except Exception as e:
            print('数据无法转化为字符串，请核查：', type(e), e, data)

    def set_db_data(self, data: Union[tuple, list], index_or_colname=None):
        """
        设置数据库数据
        :param data: 数据
        :param index_or_colname: 索引或列名
        :return:
        """
        if not index_or_colname:
            index_or_colname = 0
        if not isinstance(index_or_colname, int) and not isinstance(index_or_colname, str):
            index_or_colname = 0
        d_type = get_db_data_type(data)
        if d_type == 1:  # as_dict()
            if isinstance(index_or_colname, int) and len(data[0]) > index_or_colname >= 0:
                self.set_data([list(d.values())[index_or_colname] for d in data])
            elif isinstance(index_or_colname, str) and index_or_colname in data[0]:
                self.set_data([d[index_or_colname] for d in data])
            else:
                print(">>> set_db_data err: index_or_colname参数不符合规范")
        elif d_type == 2:  # as_list()
            if isinstance(index_or_colname, int) and len(data[0]) > index_or_colname >= 0:
                self.set_data([d[index_or_colname] for d in data[1]])
            elif isinstance(index_or_colname, str) and index_or_colname in data[0]:
                self.set_data([d[data[0].index(index_or_colname)] for d in data[1]])
            else:
                print(">>> set_db_data err: index_or_colname参数不符合规范")
        else:
            print(">>> set_db_data err:传入数据不符合规范，必须为DBResult的as_dict()或者as_list()类型")

    def get_data(self) -> list:
        t = []
        for i in range(self.widget.count()):
            t_name = self.widget.item(i).data(Qt.UserRole)
            t_text = self.widget.item(i).text()
            try:
                if t_name == 'str':
                    t.append(t_text)
                elif t_name == 'bool':
                    t.append(eval(t_text.title()))
                else:
                    t.append(eval(t_text, globals(), locals()))
            except Exception:
                t.append(t_text)
        return t

    def _get_row(self, row: Optional[int] = None) -> int:
        if row is None:
            return self.get_current_row()
        else:
            try:
                return int(row)
            except ValueError as e:
                print('请使用正确的行数：', e)

    def set_text(self, text: str, row: Optional[int] = None):
        row = self._get_row(row)
        if row < self.get_count():
            self.widget.item(row).setText(str(text))

    def get_text(self, row: Optional[int] = None) -> Optional[str]:
        row = self._get_row(row)
        if row < self.get_count():
            return self.widget.item(row).text()

    def append(self, text: str):
        lastRow = self.get_row_count()
        self.widget.addItem(str(text))
        self.widget.setCurrentRow(lastRow)

    def insert(self, text: str, row: Optional[int] = None):
        row = self._get_row(row)
        if row < self.get_count():
            self.widget.insertItem(row, str(text))
            self.widget.setCurrentRow(row)

    def update(self, text: str, row: Optional[int] = None):
        row = self._get_row(row)
        if row < self.get_count():
            self.set_text(text, row)

    def delete(self, row: Optional[int] = None):
        cur_row = self._get_row(row)
        self.widget.takeItem(cur_row)

    def get_count(self):
        return self.widget.count()

    def get_selected(self):
        return tuple([item.text() for item in self.widget.selectedItems()])

    def set_tips(self, tips: str, row: Optional[int] = None):
        row = self._get_row(row)
        if row < self.get_count():
            self.widget.item(row).setToolTip(tips)


class TTableWidgetDelegate(QtWidgets.QItemDelegate):
    IntMinimum = -100000
    IntMaximum = 100000
    IntSingleStep = 1
    FloatMinimum = -100000.0
    FloatMaximum = 100000.0
    FloatSingleStep = 0.1

    def createEditor(self, parent, option, index):
        # 创建一个QLineEdit对象
        if index.data(Qt.UserRole) == 'str':
            editor = QtWidgets.QLineEdit(parent)
        elif index.data(Qt.UserRole) == 'int':
            editor = QtWidgets.QSpinBox(parent)
            editor.setMinimum(self.IntMinimum)
            editor.setMaximum(self.IntMaximum)
            editor.setSingleStep(self.IntSingleStep)

        elif index.data(Qt.UserRole) == 'float':
            editor = QtWidgets.QDoubleSpinBox(parent)
            editor.setMinimum(self.FloatMinimum)
            editor.setMaximum(self.FloatMaximum)
            editor.setSingleStep(self.FloatSingleStep)
        elif index.data(Qt.UserRole) == 'bool':
            editor = QtWidgets.QCheckBox(parent)

        else:
            editor = QtWidgets.QLineEdit(parent)
        return editor

    def setEditorData(self, editor, index):
        # 获取单元格的数据
        value = index.data(Qt.DisplayRole)
        # 设置编辑框的文本
        if index.data(Qt.UserRole) == 'str':
            editor.setText(str(value))
        elif index.data(Qt.UserRole) == 'int':
            editor.setValue(int(value))
        elif index.data(Qt.UserRole) == 'float':
            editor.setValue(float(value))
        elif index.data(Qt.UserRole) == 'bool':
            editor.setChecked(bool(value))
        else:
            editor.setText(str(value))

    def setModelData(self, editor, model, index):
        # 获取编辑框的文本
        if index.data(Qt.UserRole) == 'str':
            text = editor.text()
        elif index.data(Qt.UserRole) == 'int':
            text = editor.value()
        elif index.data(Qt.UserRole) == 'float':
            text = editor.value()
        elif index.data(Qt.UserRole) == 'bool':
            text = editor.isChecked()
        else:
            text = editor.text()

        # 设置单元格的数据
        model.setData(index, text)

    def updateEditorGeometry(self, editor, option, index):
        # 设置编辑框的大小和位置
        editor.setGeometry(option.rect)


class TTableWidget(BaseDataWidget):
    def __init__(self,
                 table_widget: QtWidgets.QTableWidget,
                 row=0, col=0,
                 data: Optional[Union[tuple, list]] = None,
                 header: Optional[Union[list, tuple]] = None,
                 edit: bool = False,
                 select_mode: SelectionMode = SelectionMode.Single,
                 select_behavior: SelectionBehavior = SelectionBehavior.SelectRows,
                 parent: Optional[QtCore.QObject] = None):
        self.header = header
        super(TTableWidget, self).__init__(table_widget, [],
                                           edit, select_mode, select_behavior, parent)
        self.set_row_count(row)
        self.set_col_count(col)
        if header:
            self.set_header(header)
        self.set_data(data)

    def set_row_count(self, n: int):
        if n >= 0:
            self.widget.setRowCount(n)

    def set_col_count(self, n: int):
        if n >= 0:
            self.widget.setColumnCount(n)

    def get_cell_data(self, row: int, col: int) -> Any:
        """
        获取指定行、列的单元格数据
        """
        if row < self.get_row_count() \
                and col < self.get_col_count():
            item: QtWidgets.QTableWidgetItem = self.widget.item(row, col)
            if item:
                ttype = item.data(QtCore.Qt.UserRole)
                try:
                    if ttype == 'str':
                        return item.text()
                    elif ttype == 'bool':
                        return eval(item.text().title())
                    else:
                        return eval(item.text(), globals(), locals())
                except Exception as e:
                    return item.text()
                # if hasattr(builtins, ttype):
                #     # return getattr(builtins, ttype)(item.text())
                #     return eval(item.text(), globals(), locals())
                # else:
                #     return item.text()
            else:
                item = QtWidgets.QTableWidgetItem()
                item.setText('')
                self.widget.setItem(row, col, item)
                return ''
        else:
            return None

    def set_cell_data(self, row: int, col: int, data: Any):
        """

        """
        try:
            if row < self.get_row_count() \
                    and col < self.get_col_count():
                item = self.widget.item(row, col)
                if item:
                    item.setText(str(data))
                else:
                    item = QtWidgets.QTableWidgetItem()
                    item.setText(str(data))
                    self.widget.setItem(row, col, item)
                item.setData(Qt.UserRole, data.__class__.__name__)
            else:
                print('check row or col!')
        except Exception as e:
            print(e)

    def set_header(self, header: Union[list, tuple]):
        if header:
            self.set_col_count(len(header))
            self.header = header
            self.widget.setHorizontalHeaderLabels(header)

    def get_header(self):
        try:
            if self.get_col_count() > 0:
                if not self.header:
                    self.header = tuple(range(self.get_col_count()))
                    self.set_header(self.header)
                # return [self.widget.horizontalHeaderItem(c).text()
                #         for c in range(self.get_col_count())]
                return self.header

            else:
                return []
        except Exception as e:
            print(e)
            return []

    def set_data(self, data: Union[tuple, list]):
        if data:
            _row = len(data)
            _col = len(data[0])
            if not self.header:
                self.header = tuple([str(i + 1) for i in range(_col)])
            if len(self.header) != _col:
                print('表格标题数目和表格数据列数不一致！')
                return
            self.clear()
            self.set_col_count(_col)
            self.set_row_count(_row)
            self.widget.setHorizontalHeaderLabels(self.header)
            # w: QTableWidget = self.widget
            for i in range(_row):
                for c in range(_col):
                    self.set_cell_data(i, c, data[i][c])

    def set_db_data(self, data: Union[tuple, list], start: int = 0, end: int = 0):
        def _set(tdata):
            if len(tdata[0]) > end > start >= 0:
                self.set_data([_d[start:end] for _d in tdata])
            else:
                self.set_data(tdata)

        _d_type = get_db_data_type(data)
        if _d_type == 1:
            _data = []
            header = list(data[0].keys())
            self.set_col_count(len(header))
            self.set_row_count(len(data))
            self.set_header(header)
            for d in data:
                _data.append([d.get(k) for k in header])
            _set(_data)
        elif _d_type == 2:
            self.set_col_count(len(data[0]))
            self.set_row_count(len(data[1]))
            self.set_header(data[0])
            _set(data[1])
        else:
            print(">>> set_db_data err:传入数据不符合规范，必须为DBResult的as_dict()或者as_list()类型")

    def get_data(self):
        data = []
        for r in range(self.get_row_count()):
            data.append([self.get_cell_data(r, c)
                         for c in range(self.get_col_count())])
        return data

    def get_row_data(self, row: int) -> dict:
        """
        获取指定行的数据。

        :param row: 要获取数据的行索引
        :return: 包含行数据的字典，如果发生异常则返回空字典
        """
        if 0 <= row < self.get_row_count():
            try:
                return dict(zip(self.get_header(),
                                [self.get_cell_data(row, i)
                                 for i in range(self.get_col_count())
                                 if self.widget.item(row, i)]))
            except Exception as e:
                print(f"Error in get_row_data: {e}")
                return {}
        else:
            return {}

    def append_row(self, data: Union[list, tuple, dict]):
        """
        在表格末尾插入数据

        Args:
            data: 待插入的数据，类型可以是list、tuple或dict

        Returns:
            None
        """
        if len(data) == self.get_col_count():
            row = self.get_row_count()
            self.set_row_count(row + 1)
            if isinstance(data, (tuple, list)):
                for i, d in enumerate(data):
                    self.set_cell_data(row, i, str(d))
            elif isinstance(data, dict):
                for col, t in enumerate(self.get_header()):
                    self.set_cell_data(row, col, data.get(t, ''))

    def insert_row(self, data: Union[list, tuple, dict], row: int):
        """
        在指定行位置插入数据

        Args:
            data: 待插入的数据，类型可以是list、tuple或dict
            row: 插入的位置行号

        Returns:
            None
        """
        tdata = self.get_data()
        if isinstance(data, (tuple, list)):
            tdata.insert(row, data)
        elif isinstance(data, dict):
            tdata.insert(row, [data.get(t, '') for t in self.get_header()])
        self.set_data(tdata)

    def delete_row(self, row: int):
        if 0 <= row < self.get_row_count():
            self.widget.removeRow(row)

    def hide_columns(self, index: tuple, hide: bool = True):
        for i in index:
            if 0 <= i < self.get_col_count():
                self.widget.setColumnHidden(i, hide)

    def hide_rows(self, index: tuple, hide: bool = True):
        for i in index:
            if 0 <= i < self.get_row_count():
                self.widget.setRowHidden(i, hide)

    def resize_to_contents(self):
        self.widget.resizeColumnsToContents()
        self.widget.resizeRowsToContents()

    def clear_content(self):
        self.widget.clearContents()

    def clear(self):
        super().clear()
        self.set_row_count(0)

    def set_current_cell(self, row, col):
        self.widget.setCurrentCell(row, col)

    def get_selected(self):
        tmp = set()
        for item in self.widget.selectedItems():
            tmp.add(item.row())
        return tuple([self.get_row_data(row) for row in tmp])


class TTreeWidget(BaseDataWidget):
    def __init__(self,
                 tree_widget: QtWidgets.QTreeWidget,
                 data: Optional[Union[list, tuple]] = None,
                 header: Optional[Union[list, tuple]] = None,
                 edit: bool = False,
                 select_mode: SelectionMode = SelectionMode.Single,
                 select_behavior: SelectionBehavior = SelectionBehavior.SelectRows,
                 parent: Optional[QtCore.QObject] = None):
        self.__tree_data = {}
        super(TTreeWidget, self).__init__(tree_widget, data,
                                          edit, select_mode, select_behavior, parent)
        self.set_header(header)

    def set_header(self, header: Union[list, tuple]):
        if not header:
            return
        if type(header) is str:
            header = [header]
        if type(header) is list or type(header) is tuple:
            self.widget.setHeaderLabels(header)
        if not self.widget.header().isVisible():
            self.widget.header().setVisible(True)
        self.resize_to_contents()

    def get_header(self):
        """
        获取表头
        """
        item = self.widget.headerItem()
        return [item.text(i) for i in range(item.columnCount())]

    def set_data(self, data: Union[list, tuple]):
        """
        设置数据
        """
        if data:
            try:
                # (id, data, pid)
                _data = tuple(data)
                # self.__tree_data = dict([(t[0], (t[1], t[2])) for t in _data])
            except Exception as e:
                print(e)
                return

            def _get_data_list(t):
                return [t] if type(t) is str else list(t)
            self.clear()
            if self.editable:
                flags = QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
            else:
                flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
            for t in _data:
                if len(t) == 3 and t[2] is not None:
                    if t[2] == 0:
                        item = QtWidgets.QTreeWidgetItem(self.widget.invisibleRootItem(),
                                                         _get_data_list(t[1]))
                        item.setData(0, QtCore.Qt.UserRole, t[0])
                        item.setFlags(flags)
                        self.__tree_data[t[0]] = item
                    else:
                        pitem = self.__tree_data.get(t[2], None)
                        if pitem:
                            item = QtWidgets.QTreeWidgetItem(pitem, _get_data_list(t[1]))
                            item.setData(0, QtCore.Qt.UserRole, t[0])
                            item.setFlags(flags)
                            self.__tree_data[t[0]] = item
                        else:
                            print('错误：为查找到父节点！可能父节点还未创建，请检查数据')
                            return

            self.resize_to_contents()

    def set_db_data(self, data: Union[tuple, list], did='id', pid='pid'):
        """
        设置数据库数据
        """
        _d_type = get_db_data_type(data)
        if _d_type == 1:  # dict
            t_data = []
            t_header = []
            for d in data:
                _did = d.pop(did, None)
                _pid = d.pop(pid, None)
                t_data.append([_did, list(d.values()), _pid])
                if not t_header:
                    t_header = list(d.keys())
            self.set_data(t_data)
            self.set_header(t_header)
        elif _d_type == 2:
            t_data = []
            _did_index = data[0].index(did)
            _pid_index = data[0].index(pid)
            for r in data[1]:
                _r = list(r)
                _r.remove(r[_did_index])
                _r.remove(r[_pid_index])
                t_data.append([r[_did_index], _r, r[_pid_index]])
            self.set_data(t_data)
            t_header = list(data[0][:])
            t_header.remove(did)
            t_header.remove(pid)
            self.set_header(t_header)
        else:
            print(">>> set_db_data err:传入数据不符合规范，必须为DBResult的as_dict()或者as_list()类型")

    @staticmethod
    def _get_pid(item: QtWidgets.QTreeWidgetItem):
        if item:
            pitem = item.parent()
            if pitem:
                return pitem.data(0, QtCore.Qt.UserRole)
            else:
                return 0

    def _get_item_text(self, item: QtWidgets.QTreeWidgetItem):
        if item:
            return [item.text(i) for i in range(self.get_col_count())]

    def _get_item_data(self, item: QtWidgets.QTreeWidgetItem):
        if item:
            return item.data(0, QtCore.Qt.UserRole), self._get_item_text(item), self._get_pid(item)

    def _get_row_item(self, row=0, pid=0):
        if not self.has_id(pid):
            return None
        else:
            return self.__tree_data.get(pid).child(row)

    def get_data(self):
        return [self._get_item_data(item)
                for item in self.__tree_data.values()]

    def get_iterator(self):
        return QtWidgets.QTreeWidgetItemIterator(self.widget)

    def has_id(self, did):
        return did in self.__tree_data.keys()

    def resize_to_contents(self, col: int = 0):
        self.widget.resizeColumnToContents(col)

    def get_selected(self):

        return tuple([self._get_item_data(item) for item in self.widget.selectedItems()])

    def get_current_item(self):
        item = self.widget.currentItem()
        if item:
            return self._get_item_data(item)

    def get_row_data(self, row=0, pid=0):
        item = self._get_row_item(row, pid)
        if item:
            return self._get_item_data(item)

    def get_children(self, did):
        return [self.get_row_data(r, did)
                for r in range(self.__tree_data[did].childCount())]

    def add_item(self, rdata: Union[tuple, list, str], pid=0):
        if type(rdata) is str:
            rdata = [rdata]
        if rdata and len(rdata) == self.get_col_count():
            did = len(self.__tree_data)
            pitem = self.__tree_data.get(pid,
                                         self.widget.invisibleRootItem())
            item = QtWidgets.QTreeWidgetItem(pitem, rdata)
            item.setData(0, QtCore.Qt.UserRole, did)
            self.__tree_data[did] = item
            self.resize_to_contents()
            return did

    def del_item(self, did: int):
        td: QtWidgets.QTreeWidgetItem = self.__tree_data.pop(did, None)
        if td and td.childCount() == 0:
            td.parent().removeChild(td)
        else:
            print('因为存在子节点，删除失败')
            return

    def clear(self):
        self.__tree_data.clear()
        self.widget.clear()


def _get_qdate(t):
    """
    根据输入的参数t获取对应的QDate对象

    参数:
    t (QtCore.QDate, str, int, tuple, datetime.date): 输入的参数

    返回:
    QtCore.QDate: 对应的QDate对象

    异常:
    TypeError: 不支持的类型
    """
    if isinstance(t, QtCore.QDate):
        return t
    elif isinstance(t, datetime.date):
        return QtCore.QDate(t.year, t.month, t.day)
    elif isinstance(t, str):
        if '-' in t:
            return QtCore.QDate(*[int(i) for i in t.split('-')])
        elif '/' in t:
            return QtCore.QDate(*[int(i) for i in t.split('/')])
    elif isinstance(t, int):
        return QtCore.QDate(t)
    elif isinstance(t, tuple):
        return QtCore.QDate(*t)
    else:
        raise TypeError('不支持的类型')


def _get_qtime(t):
    """
    根据输入的参数t获取对应的QTime对象

    参数:
    t: 输入的参数，可以是以下类型之一:
        - QtCore.QTime: 直接返回该对象
        - str: 如果字符串包含冒号，则解析为时间字符串，例如"HH:MM:SS"，并返回对应的QTime对象
        - 如果字符串包含空格，则解析为时间字符串，例如"HH MM SS"，并返回对应的QTime对象
        - int: 解析为时间字符串，例如"HHMMSS"，并返回对应的QTime对象
        - tuple: 解析为时间字符串，例如("HH", "MM", "SS")，并返回对应的QTime对象
        - 其他类型: 抛出TypeError异常

    返回:
    返回对应的QTime对象
    """
    if isinstance(t, QtCore.QTime):
        return t
    elif isinstance(t, datetime.time):
        return QtCore.QTime(t.hour, t.minute, t.second)
    elif isinstance(t, str):
        if ':' in t:
            return QtCore.QTime(*[int(i) for i in t.split(':')])
        elif ' ' in t:
            return QtCore.QTime(*[int(i) for i in t.split(' ')])
    elif isinstance(t, int):
        return QtCore.QTime(t)
    elif isinstance(t, tuple):
        return QtCore.QTime(*t)
    else:
        raise TypeError('不支持的类型')


def _get_qdatetime(t):
    """
    根据输入的参数t获取对应的QtCore.QDateTime对象

    Args:
        t: 输入的参数

    Returns:
        QtCore.QDateTime对象

    Raises:
        TypeError: 如果参数t的类型不支持
    """
    if isinstance(t, QtCore.QDateTime):
        return t
    elif isinstance(t, datetime.datetime):
        return QtCore.QDateTime(t.year, t.month, t.day, t.hour, t.minute, t.second)
    elif isinstance(t, str):
        # 如果参数t是字符串类型，将其拆分为日期和时间两部分
        _date, _time = t.split(' ')
        # 分别获取拆分后的日期和时间对应的QtCore.QDate和QtCore.QTime对象
        return QtCore.QDateTime(_get_qdate(_date), _get_qtime(_time))
    elif isinstance(t, int):
        # 如果参数t是整数类型，直接将其转换为QtCore.QDateTime对象
        return QtCore.QDateTime(t)
    elif isinstance(t, tuple):
        # 如果参数t是元组类型，将其解包后转换为QtCore.QDateTime对象
        return QtCore.QDateTime(*t)
    else:
        # 如果参数t的类型不支持，抛出TypeError异常
        raise TypeError('不支持的类型')


def _set_spinbox_val(obj: QtWidgets.QSpinBox, val):
    if isinstance(val, float):
        obj.setValue(int(val))
    elif isinstance(val, int):
        obj.setValue(val)
    elif isinstance(val, tuple) or isinstance(val, list) and len(val) == 3:
        obj.setMinimum(val[1])
        obj.setMaximum(val[2])
        obj.setValue(val[0])
    else:
        raise TypeError('不支持的类型')


def _set_doublespinbox_val(obj: QtWidgets.QDoubleSpinBox, val):
    if isinstance(val, float):
        obj.setValue(val)
    elif isinstance(val, int):
        obj.setValue(val)
    elif isinstance(val, tuple) or isinstance(val, list) and len(val) == 3:
        obj.setMinimum(val[1])
        obj.setMaximum(val[2])
        obj.setValue(val[0])
    else:
        raise TypeError('不支持的类型')


def _set_slider_val(obj: QtWidgets.QSlider, val):
    if isinstance(val, float):
        obj.setValue(int(val))
    elif isinstance(val, int):
        obj.setValue(val)
    elif isinstance(val, tuple) or isinstance(val, list) and len(val) == 3:
        obj.setMinimum(val[1])
        obj.setMaximum(val[2])
        obj.setValue(val[0])
    else:
        raise TypeError('不支持的类型')


def _set_combobox_val(obj, val):
    if isinstance(val, str):
        obj.setCurrentText(val)
    elif isinstance(val, int):
        obj.setCurrentIndex(val)


class TDataProxy:
    """
    控件数据代理类，使用objectname访问/设置控件的text或value值
    支持Qt控件列表：'QComboBox', 'QFontComboBox', 'QLineEdit', 'QTextEdit',
    'QPlainTextEdit', 'QSpinBox', 'QDoubleSpinBox', 'QTimeEdit', 'QDateEdit',
    'QDateTimeEdit', 'QDial', 'QSlider', 'QKeySequenceEdit', 'QLabel', 'QTextBrowser',
    'QCalendarWidget', 'QLCDNumber', 'QProgressBar', 'QAbstractButton', 'QListWidget',
    'QTableWidget', 'QTreeWidget'。
    示例演示：
        from PyQt5.QtWidgets import *
        from pgtools.plugins.client.qtwidget_helper import TDataProxy

        app = QApplication([])

        w = QWidget()
        w.resize(800, 600)
        t = QLabel("Hello", w)
        t.setObjectName("t")  # important！
        t.setGeometry(10, 10, 200, 100)
        t1 = QLineEdit("show", w)
        t1.setObjectName("t1")  # important！
        t1.setGeometry(10, 160, 500, 100)

        d = TDataProxy(t, t1)
        print(d.get_all_support())
        print(d.data)  # {'t': 'Hello', 't1': 'dfdfs'}
        print(d.t, d.t1)
        print(d['t', 't1'])
        # 修改
        d.t = "lable 演示"
        d.t1 = d.t1 + "演示"
        # 批量修改
        d.data = {'t': 'Hello 哈哈', 't1': '测试'}
        w.show()
        app.exec_()

    """
    _method = {
        # input
        QtWidgets.QComboBox.__name__: {
            'get': lambda obj: obj.currentText(),
            'set': lambda obj, val: obj.setCurrentText(val),
        },
        QtWidgets.QFontComboBox.__name__: {
            'get': lambda obj: obj.currentFont().family(),
            'set': lambda obj, val: obj.setCurrentFont(QtGui.QFont(val)),
        },
        QtWidgets.QLineEdit.__name__: {
            'get': lambda obj: obj.text().strip(),
            'set': lambda obj, val: obj.setText(val),
        },
        QtWidgets.QTextEdit.__name__: {
            'get': lambda obj: obj.toPlainText(),
            'set': lambda obj, val: obj.setPlainText(val),
        },
        QtWidgets.QPlainTextEdit.__name__: {
            'get': lambda obj: obj.toPlainText(),
            'set': lambda obj, val: obj.setPlainText(val),
        },
        QtWidgets.QSpinBox.__name__: {
            'get': lambda obj: obj.value(),
            'set': _set_spinbox_val,
        },
        QtWidgets.QDoubleSpinBox.__name__: {
            'get': lambda obj: obj.value(),
            'set': _set_doublespinbox_val,
        },
        QtWidgets.QTimeEdit.__name__: {
            'get': lambda obj: obj.text(),
            'set': lambda obj, t: obj.setTime(_get_qtime(t)),
        },
        QtWidgets.QDateEdit.__name__: {
            'get': lambda obj: obj.text(),
            'set': lambda obj, t: obj.setDate(_get_qdate(t)),
        },
        QtWidgets.QDateTimeEdit.__name__: {
            'get': lambda obj: obj.text(),
            """
            set datatime string in format:
            'yyyy-MM-dd hh:mm:ss'
            'yyyy/MM/dd hh:mm:ss'
            """
            'set': lambda obj, t: obj.setDateTime(_get_qdatetime(t)),
        },
        QtWidgets.QDial.__name__: {
            'get': lambda obj: obj.notchTarget(),
            'set': lambda obj, val: obj.setNotchTarget(val),
            'type': 'float'
        },
        QtWidgets.QSlider.__name__: {
            'get': lambda obj: obj.value(),
            'set': lambda obj, val: obj.setValue(val),
        },
        QtWidgets.QKeySequenceEdit.__name__: {
            'get': lambda obj: obj.keySequence().toString(),
            'set': lambda obj, val: obj.setKeySequence(QtGui.QKeySequence.fromString(val)),
        },

        # dispay
        QtWidgets.QLabel.__name__: {
            'get': lambda obj: obj.text(),
            'set': lambda obj, val: obj.setText(val),
        },
        QtWidgets.QTextBrowser.__name__: {
            'get': lambda obj: obj.toPlainText(),
            'set': lambda obj, val: obj.setPlainText(val),
        },
        # QGraphicsWidget.__name__: {
        #     'get': 'text',
        #     'set': 'setText',
        # },
        QtWidgets.QCalendarWidget.__name__: {
            'get': lambda obj: obj.selectedDate().toString(QtCore.Qt.ISODate),
            'set': lambda obj, val: obj.setSelectedDate(QtCore.QDate.fromString(val, QtCore.Qt.ISODate)),
        },
        QtWidgets.QLCDNumber.__name__: {
            'get': lambda obj: obj.value(),
            'set': lambda obj, val: obj.display(val),
        },
        QtWidgets.QProgressBar.__name__: {
            'get': lambda obj: obj.value(),
            'set': lambda obj, val: obj.setValue(val),
        },

        QtWidgets.QAbstractButton.__name__: {
            'get': lambda obj: obj.text(),
            'set': lambda obj, val: obj.setText(val),
        },
        QtWidgets.QListWidget.__name__: {
            'get': lambda obj: TListWidget(obj).get_data(),
            'set': lambda obj, vals: TListWidget(obj).set_data(vals)
        },
        QtWidgets.QTableWidget.__name__: {
            'get': lambda obj: TTableWidget(obj).get_data(),
            'set': lambda obj, vals: TTableWidget(obj).set_data(vals)
        },
        QtWidgets.QTreeWidget.__name__: {
            'get': lambda obj: TTreeWidget(obj).get_data(),
            'set': lambda obj, vals: TTreeWidget(obj).set_data(vals)
        },
        QtWidgets.QCheckBox.__name__: {
            'get': lambda obj: obj.isChecked(),
            'set': lambda obj, val: obj.setChecked(val)
        },
        QtWidgets.QRadioButton.__name__: {
            'get': lambda obj: obj.isChecked(),
            'set': lambda obj, val: obj.setChecked(val)
        }
    }

    def __init__(self, *args) -> None:
        """
        :param args: list of QT widgets
        :type args: QWidget's subclasses list
        """
        self._qt_widgets = dict([(q.objectName(), q) for q in args])

    @classmethod
    def _get_val(cls, q):
        fun_dict = cls._method.get(q.__class__.__name__, None)
        if fun_dict:
            fun = fun_dict.get('get', None)
            return fun(q) if callable(fun) else None
        else:
            print('处理%s发生错误：不支持类%s的数据绑定' %
                  (q.objectName(), q.__class__.__name__))
            return None

    @classmethod
    def _set_val(cls, q: QtWidgets.QWidget, val):
        # print(q.__class__.__name__)
        fun_dict = cls._method.get(q.__class__.__name__, None)
        if fun_dict:
            fun = fun_dict.get('set', None)
            return fun(q, val) if callable(fun) else None
        else:
            # print('处理%s发生错误：不支持类%s的数据绑定' %
            #       (q.objectName(), q.__class__.__name__))
            raise AttributeError('处理%s发生错误：不支持类%s的数据绑定' %
                                 (q.objectName(), q.__class__.__name__))
            # return None

    @property
    def data(self):
        d = {}
        for k, v in self._qt_widgets.items():
            d[k] = self._get_val(v)
        return d

    @data.setter
    def data(self, vals: dict):
        for k, v in vals.items():
            self._set_val(self._qt_widgets[k], v)

    def __getattr__(self, name):
        if name != '_qt_widgets' and hasattr(self, '_qt_widgets'):
            return self._get_val(self._qt_widgets[name])

    def __setattr__(self, name, value):
        if name != '_qt_widgets' and hasattr(self, '_qt_widgets') and \
                name in self._qt_widgets.keys():
            self._set_val(self._qt_widgets[name], value)
        else:
            super().__setattr__(name, value)

    def __getitem__(self, name):
        if hasattr(self, '_qt_widgets'):
            if name in self._qt_widgets.keys():
                return self._get_val(self._qt_widgets[name])
            elif isinstance(name, tuple):
                return dict(zip(name, [self._get_val(self._qt_widgets[n]) for n in name]))

    def __setitem__(self, name, value):
        if hasattr(self, '_qt_widgets'):
            self._set_val(self._qt_widgets[name], value)

    def __iter__(self):
        return iter(self._qt_widgets.keys())

    def __len__(self):
        return len(self._qt_widgets)

    def __contains__(self, name):
        return name in self._qt_widgets.keys()

    def is_support_widget(self, name):
        return name in self._method.keys()

    def get_all_support_widget(self):
        return tuple(self._method.keys())
